# -*- coding:utf-8 -*-

"""
------------------------------------
# @FileName    :research_judgement.py
# @Time        :2025/11/17
# @Author      :assistant
# @description :线索重复性判断模块
------------------------------------
"""
import asyncio
import json
import sys
import os
import re
import yaml
import time
from typing import List, Dict, Any, Optional, Union

# 添加父目录到路径，以便导入call_llm
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from call_llm import _run_all_configs_once

# 加载配置文件
def load_config():
    """从config.yaml加载配置"""
    try:
        config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.yaml")
        if os.path.exists(config_path):
            with open(config_path, "r", encoding="utf-8") as f:
                return yaml.safe_load(f) or {}
    except Exception as e:
        print(f"加载 config.yaml 失败：{e}")
    return {}

CONFIG = load_config()


async def parse_petition_content(
    key_extract_content: str,
    name: Optional[str] = None,
    concurrent: Optional[int] = None,
    prompt_key: str = "petition_parse",  # message 二声
) -> Dict[str, Any]:
    """
    使用LLM解析信访文本内容
    
    Args:
        key_extract_content: 信访文本内容（纯字符串）
        name: 模型名称（可选，默认qwen3-14b）
        concurrent: 并发数（可选）
        prompt_key: config.yaml中LLM_PROMPTS的键名（默认"petition_parse"）
    
    Returns:
        Dict: 解析后的结构化JSON数据
    """
    
    # 从配置文件加载提示词
    llm_prompts = CONFIG.get("LLM_PROMPTS", {})
    prompt_config = llm_prompts.get(prompt_key, {})
    
    system_prompt = prompt_config.get("system_prompt", "")
    user_prompt_template = prompt_config.get("user_prompt", "")
    
    # 如果配置文件中没有提示词，使用默认提示词（兜底）
    if not system_prompt:
        print(f"警告：未找到配置项 LLM_PROMPTS.{prompt_key}.system_prompt，使用默认提示词")
        system_prompt = "你是一个专业的信访文件分析助手。"
    
    if not user_prompt_template:
        print(f"警告：未找到配置项 LLM_PROMPTS.{prompt_key}.user_prompt，使用默认提示词")
        user_prompt_template = "{key_extract_content}"
    
    # 替换user_prompt中的占位符
    user_prompt = user_prompt_template.format(key_extract_content=key_extract_content)
    
    # 调用LLM进行解析
    try:
        result = await _run_all_configs_once(
            name=name,
            system_prompt=system_prompt,
            user_prompt=user_prompt,
            concurrent=concurrent or 1
        )
        
        if result and len(result) > 0:
            llm_response = result[0]
            print(f"\n=== LLM解析结果 ===\n{llm_response}\n")
            
            # 清理并解析JSON  这里应该可以解决json格式错乱问题  # todo  {'res': [{'reported_entity': '稻国烟草局霞山分
            cleaned_json_str = clean_json_response(llm_response)
            parsed_data = json.loads(cleaned_json_str)
            
            return parsed_data
        else:
            print("LLM未返回有效结果")
            return {"res": []}
            
    except json.JSONDecodeError as e:
        print(f"JSON解析失败: {e}")
        print(f"原始响应: {llm_response if 'llm_response' in locals() else 'N/A'}")
        return {"res": [], "error": "JSON解析失败"}
    except Exception as e:
        print(f"解析过程出错: {e}")
        return {"res": [], "error": str(e)}


def clean_json_response(response_text):
    """
    从包含<think>标签和Markdown代码块的响应文本中提取纯JSON内容
    """
    if response_text is None:
        return ""
    if not isinstance(response_text, (str, bytes)):
        response_text = str(response_text)
    
    JSON_CODE_BLOCK_START = '```json'
    CODE_BLOCK_END = '```'
    THINK_TAG_PATTERN = r'<think>.*?</think>'

    cleaned_text = re.sub(THINK_TAG_PATTERN, '', response_text, flags=re.DOTALL)

    if JSON_CODE_BLOCK_START in cleaned_text:
        cleaned_text = cleaned_text.split(JSON_CODE_BLOCK_START, 1)[1]
    if CODE_BLOCK_END in cleaned_text:
        cleaned_text = cleaned_text.split(CODE_BLOCK_END, 1)[0]

    return cleaned_text.strip()


async def judgement_clue_repeat(
    clue_list: Union[str, List[str]],
    key_extract_content: str,
    name: Optional[str] = None,
    system_prompt: Optional[str] = None,
    user_prompt: Optional[str] = None,
    concurrent: Optional[int] = None,
):
    """
    判断线索是否重复
    
    Args:
        clue_list: 线索列表（List[str]）或线索字符串（str，多个线索用|或换行符分隔）
        key_extract_content: 信访纯文本内容（会自动调用LLM解析）
        name: 模型名称（可选，默认qwen3-14b）
        system_prompt: 自定义系统提示词（可选，用于重复判断）
        user_prompt: 自定义用户提示词（可选，用于重复判断）
        concurrent: 并发数（可选）
    
    Returns:
        Dict: 处理后的key_extract_content，包含repeat_flag字段
    """
    
    # 步骤1: 使用LLM解析信访纯文本为结构化JSON
    print("使用LLM进行信访文本解析...")
    start_time = time.time()
    parsed_result = await parse_petition_content(
        key_extract_content=key_extract_content,
        name=name,
        concurrent=concurrent
    )
    parse_duration = time.time() - start_time
    print(f"信访文本解析完成，耗时: {parse_duration:.2f}秒")
    
    # 步骤2: 清理并解析JSON（如果LLM返回的是字符串格式）
    if isinstance(parsed_result, str):
        cleaned_json_str = clean_json_response(parsed_result)
        key_extract_content = json.loads(cleaned_json_str)
    else:
        key_extract_content = parsed_result
    
    print(f"LLM解析完成，提取到 {len(key_extract_content.get('res', []))} 个实体")
    
    # 处理clue_list：支持列表或字符串（向后兼容）
    if isinstance(clue_list, list):
        # 直接使用列表（推荐方式）
        clue_list_array = clue_list
        print(f"\nclue_list已是列表格式，共 {len(clue_list_array)} 条线索")
    else:
        # 字符串格式（向后兼容，已废弃，建议使用列表）
        print(f"\n警告：clue_list传入字符串格式已废弃，建议使用列表格式")
        clue_list_array = [line.strip() for line in clue_list.split('|') if line.strip()] if '|' in clue_list else [clue_list]
        print(f"解析为 {len(clue_list_array)} 条线索")
    
    # 打印线索预览（前3条）
    for idx, clue in enumerate(clue_list_array[:3]):
        print(f"线索{idx+1} (前100字符): {clue[:100]}...")
    if len(clue_list_array) > 3:
        print(f"... 还有 {len(clue_list_array)-3} 条线索未显示")
    
    # ========== 以下为旧的字符串解析逻辑（已废弃，保留注释供参考） ==========
    # if isinstance(clue_list, str):
    #     print(f"\n=== 解析clue_list ===")
    #     print(f"原始clue_list长度: {len(clue_list)}")
    #     print(f"原始clue_list前200字符: {repr(clue_list[:200])}")
    #     
    #     # 尝试多种分隔符：优先用 | 分隔，然后用"知识库线索内容："分隔，最后用换行符
    #     if '|' in clue_list:
    #         # 使用 | 分隔
    #         clue_list_array = [line.strip() for line in clue_list.split('|') if line.strip()]
    #         print(f"使用 | 分隔，得到 {len(clue_list_array)} 条线索")
    #     elif '知识库线索内容：' in clue_list:
    #         # 使用"知识库线索内容："分隔
    #         clue_list_array = [line.strip() for line in clue_list.split('知识库线索内容：') if line.strip()]
    #         print(f"使用'知识库线索内容：'分隔，得到 {len(clue_list_array)} 条线索")
    #     else:
    #         # 使用换行符分隔
    #         clue_list_array = [line.strip() for line in clue_list.split('\n') if line.strip()]
    #         print(f"使用换行符分隔，得到 {len(clue_list_array)} 条线索")
    #     
    #     # 打印每条线索的前100字符
    #     for idx, clue in enumerate(clue_list_array[:3]):  # 只打印前3条
    #         print(f"线索{idx+1}前100字符: {repr(clue[:100])}")
    #     if len(clue_list_array) > 3:
    #         print(f"... 还有 {len(clue_list_array)-3} 条线索未显示")
    # else:
    #     clue_list_array = clue_list
    #     print(f"clue_list已是列表格式，共 {len(clue_list_array)} 条线索")
    # ========== 旧逻辑结束 ==========
    
    # 默认系统提示词
    default_system_prompt = """你是一个专业的信访线索分析专家，擅长判断信访内容是否重复。
你需要分析一个线索集合和一条新线索，判断新线索是否与集合中的任何一条线索重复。

判断标准：
1. **时间一致性**：时间是否一致或高度重合（例如：都是2024年2月-5月）
2. **地点一致性**：地点是否一致（例如：都在霞山市或邻市）
3. **事件一致性**：核心事件是否相同（例如：都是关于公车私用）

重要说明：
- 线索集合中包含多条线索，**只要新线索与集合中任意一条线索在时间、地点、事件三方面都一致，就判定为重复**
- 不需要与所有线索都一致，只需与其中一条一致即可
- 如果新线索包含的详细信息更多，但核心时间、地点、事件一致，也应判定为重复

# 思考模式
/no_think

请严格按照以下JSON格式输出（不要添加任何其他文字）：
{
    "is_repeat": true/false,
    "reason": "判断理由，说明与哪条线索重复，以及时间、地点、事件的对比结果"
}"""
    
    # 使用自定义或默认的系统提示词
    final_system_prompt = system_prompt if system_prompt else default_system_prompt
    
    # 将clue_list_array合并成一个字符串
    clue_text = "\n".join(clue_list_array)
    
    # 获取res列表
    res_list = key_extract_content.get("res", [])
    
    # 从配置文件读取延迟时间（毫秒），默认2000ms（2秒）
    delay_ms = CONFIG.get("CLUE_REPEAT_DELAY", 2000)
    
    # 创建并发任务列表
    tasks = []
    task_info = []  # 记录每个任务对应的item索引和信息
    
    # 为线索集合添加编号（提前准备，所有任务共用）
    numbered_clues = "\n".join([f"线索{i+1}：{clue}" for i, clue in enumerate(clue_list_array)])
    # 知识库线索内容： 这个可以去掉 ；线索1：知识库线索内容："编号":"[2025]002";"时间":"45691"  # todo
    # 创建并发任务
    for idx, item in enumerate(res_list):
        content_summary = item.get("content_summary", [])
        content_summary_text = "\n".join(content_summary)
        
        # 构造用户提示词
        if user_prompt:
            final_user_prompt = user_prompt.format(
                clue_list=clue_text,
                content_summary=content_summary_text
            )
        else:
            final_user_prompt = f"""请判断待判断的新线索是否与已有线索集合中的任意一条线索重复：

【已有线索集合】（共{len(clue_list_array)}条线索）：
{numbered_clues}

【待判断的新线索】：
{content_summary_text}

请逐一对比新线索与线索集合中的每条线索，分析时间、地点、事件是否一致。
只要与其中任意一条线索的时间、地点、事件三者都一致，就判定为重复。
请按照指定的JSON格式输出判断结果。"""
        
        # 创建带延迟的判断任务
        task = judge_repeat_with_delay(
            item=item,
            name=name,
            final_system_prompt=final_system_prompt,
            final_user_prompt=final_user_prompt,
            idx=idx,
            delay=delay_ms  # 从配置文件读取延迟时间
        )
        tasks.append(task)
        task_info.append((idx, item))
    
    # 并发执行所有判断任务
    if tasks:
        print(f"开始并发执行 {len(tasks)} 个线索重复判断任务（每个任务延迟{delay_ms/1000:.1f}秒递增）...")
        total_start_time = time.time()
        results = await asyncio.gather(*tasks, return_exceptions=True)
        total_duration = time.time() - total_start_time
        print(f"所有线索重复判断完成，总耗时: {total_duration:.2f}秒")
        
        # 处理结果，将判断结果写回对应的item
        for (idx, item), result in zip(task_info, results):
            # 如果是异常，标记为错误
            if isinstance(result, Exception):
                item["repeat_flag"] = "error"
                item["repeat_error"] = str(result)
            # 否则result已经在judge_repeat_with_delay中写入item了
    
    return key_extract_content


async def judge_repeat_with_delay(
    item: Dict[str, Any],
    name: Optional[str],
    final_system_prompt: str,
    final_user_prompt: str,
    idx: int,
    delay: int = 2000
) -> None:
    """
    带延迟的线索重复判断函数
    
    Args:
        item: res列表中的单个元素（会直接修改）
        name: 模型名称
        final_system_prompt: 系统提示词
        final_user_prompt: 用户提示词
        idx: 任务索引（用于计算延迟）
        delay: 延迟时间（毫秒，从config.yaml的CLUE_REPEAT_DELAY读取，默认2000ms=2秒）
    """
    # 延迟执行：0, 1*delay, 2*delay, 3*delay...
    delay_ms = idx * delay
    if delay_ms > 0:
        await asyncio.sleep(delay_ms / 1000.0)  # 转换为秒
    
    # 调用LLM进行判断
    try:
        repeat_start_time = time.time()
        result = await _run_all_configs_once(
            name=name,
            system_prompt=final_system_prompt,
            user_prompt=final_user_prompt,
            concurrent=1
        )
        repeat_duration = time.time() - repeat_start_time
        print(f"线索重复判断完成（实体: {item.get('reported_entity', '未知')}），耗时: {repeat_duration:.2f}秒")
        
        if result and len(result) > 0:
            llm_response = result[0]
            # 解析LLM返回的JSON
            try:
                # 尝试提取JSON部分
                if "```json" in llm_response:
                    json_start = llm_response.find("```json") + 7
                    json_end = llm_response.find("```", json_start)
                    json_str = llm_response[json_start:json_end].strip()
                elif "{" in llm_response and "}" in llm_response:
                    json_start = llm_response.find("{")
                    json_end = llm_response.rfind("}") + 1
                    json_str = llm_response[json_start:json_end]
                else:
                    json_str = llm_response
                
                judgment = json.loads(json_str)
                
                # 如果判定为重复，添加repeat_flag
                if judgment.get("is_repeat", False):
                    item["repeat_flag"] = "true"
                    item["repeat_reason"] = judgment.get("reason", "")
                else:
                    item["repeat_flag"] = "false"
                    
            except json.JSONDecodeError:
                # 如果解析失败，根据关键词判断
                if "true" in llm_response.lower() or "重复" in llm_response or "一致" in llm_response:
                    item["repeat_flag"] = "true"
                    item["repeat_reason"] = "LLM判断为重复"
                else:
                    item["repeat_flag"] = "false"
    except Exception as e:
        # 异常情况下，标记为未判断
        item["repeat_flag"] = "error"
        item["repeat_error"] = str(e)


# 测试代码
if __name__ == "__main__":
    # 测试1: 测试parse_petition_content函数 - 解析信访文本
    print("=" * 80)
    print("测试1: 使用LLM解析信访文本")
    print("=" * 80)
    
    test_petition_text = """
    接到匿名举报，反映稻国烟草局霞山分公司经理张明存在公车私用问题。
    举报内容称：2024年2月至5月期间，张明多次驾驶公车前往邻市办理私事。
    车辆在春节假期及每周五晚或周六上午频繁往返于霞山市与邻市。
    累计12次无派车记录，高速公路费用合计840元使用公款支付。
    
    另外，举报还反映该公司副主任李华也存在类似问题。
    李华于2024年1月2日占用粤G2K113车辆办理私事，
    非工作时间活动于住宅小区等地。
    
    信访来源：自收
    收到日期：2024年6月8日
    """
    
    parsed_result = asyncio.run(parse_petition_content(test_petition_text))
    print("\n解析结果:")
    print(json.dumps(parsed_result, ensure_ascii=False, indent=2))
    
    print("\n" + "=" * 80)
    print("测试2: 测试judgement_clue_repeat函数 - 判断线索重复（使用JSON格式）")
    print("=" * 80)
    
    # 测试数据（使用JSON字典格式）
    test_clue_list = """2024年2月至5月期间，张明多次驾驶公车前往邻市办理私事。
车辆在春节假期及每周五晚或周六上午频繁往返于霞山市与邻市。"""
    
    test_key_extract = {
        "res": [
            {
                "reported_entity": "稻国烟草局霞山分公司经理张明",
                "source": "自收",
                "reporter": "匿名",
                "receive_date": "2024年6月8日",
                "content_summary": [
                    "2024年2月至5月期间，张明多次驾驶公车前往邻市办理私事。",
                    "车辆在春节假期及每周五晚或周六上午频繁往返于霞山市与邻市。",
                    "累计12次无派车记录，高速公路费用合计840元使用公款支付。"
                ],
                "analysis_reference": "举报内容具体，包含车辆使用轨迹、高速公路费用记录及监控证据，反映张明存在公车私用行为。",
                "analysis_result": "信访反映张明公车私用问题，建议由纪律审查室组织核查。"
            }
        ]
    }
    
    result = asyncio.run(judgement_clue_repeat(
        test_clue_list,
        test_key_extract
    ))
    
    print("\n判断结果:")
    print(json.dumps(result, ensure_ascii=False, indent=2))
    
    print("\n" + "=" * 80)
    print("测试3: 完整流程 - 从纯文本到重复判断（自动调用LLM解析）")
    print("=" * 80)
    
    # 测试数据（使用纯文本格式）
    test_clue_list_3 = """知识库线索内容：2024年2月至5月期间，张明多次驾驶公车前往邻市办理私事。
知识库线索内容：车辆在春节假期及每周五晚或周六上午频繁往返于霞山市与邻市。"""
    
    # 纯文本格式的信访内容
    test_petition_text_3 = """
    接到匿名举报，反映稻国烟草局霞山分公司经理张明存在公车私用问题。
    举报内容称：2024年2月至5月期间，张明多次驾驶公车前往邻市办理私事。
    车辆在春节假期及每周五晚或周六上午频繁往返于霞山市与邻市。
    累计12次无派车记录，高速公路费用合计840元使用公款支付。
    
    信访来源：自收
    收到日期：2024年6月8日
    """
    
    # 直接传入纯文本，函数会自动调用LLM解析，然后进行重复判断
    result_3 = asyncio.run(judgement_clue_repeat(
        test_clue_list_3,
        test_petition_text_3  # 纯文本格式
    ))
    
    print("\n完整流程判断结果:")
    print(json.dumps(result_3, ensure_ascii=False, indent=2))
